/*******************************************************************************
 * Copyright (c) 2007, Geophysical Center RAS
 * Sergey Berezin, Dmitry Voitsekhovskiy
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms,
 * with or without modification, are permitted provided
 * that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * - Neither the name of the Geophysical Center RAS nor the names
 * of its contributors may be used to endorse or promote products
 * derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ******************************************************************************/
using System;
using System.Xml;

using VisualEsse;
using System.Drawing;
using System.IO;
using VisualEsse.DataEngine.Types;

namespace VisualEsse.DataEngine
{   
    public class EssePackDataProvider : EsseResponseDataProvider
    {        
        protected EsseResponse rsp = null;
        protected EsseRegion region = null;

        public EssePackDataProvider(string path, string homeDirectory) : base(homeDirectory)
        {
            Stream s = null;
            try
            {
                if (IsGZipped(path))
                    s = new System.IO.Compression.GZipStream(new FileStream(path, FileMode.Open), System.IO.Compression.CompressionMode.Decompress);
                else
                    s = new FileStream(path, FileMode.Open);

                rsp = new EsseResponse(new XmlTextReader(s));
                region = rsp.Parameters[0].Region;

                GenericMetadataProvider genericProvider = new GenericMetadataProvider(homeDirectory,
                        new TemporalGrid(region.From, region.To, region.TimeStep),
                        new GeoUniformGrid2d(
                            new GeoPoint((float)region.LatStep, (float)region.LonStep),
                            new GeoRect(ToGeoPoint(region.GridMin), ToGeoPoint(region.GridMax)))
                    );
                metadataProvider = genericProvider;

                // Fill available parameters collection
                for (int i = 0; i < rsp.Parameters.Count; i++)
                    genericProvider.AddParameter(rsp.Parameters[i].Parameter);
            }
            finally
            {   
                if(s != null)
                    s.Dispose();
            }
        }

        private GeoPoint ToGeoPoint(double2 p)
        {
            return new GeoPoint((float)p.y, (float)p.x);
        }


        public override void Dispose()
        {
            base.Dispose();
        }

        private bool IsGZipped(string path)
        {
            try
            {
                using (Stream s = new FileStream(path, FileMode.Open))
                {
                    Stream sGZip = new System.IO.Compression.GZipStream(s, System.IO.Compression.CompressionMode.Decompress);
                    XmlReader reader = XmlReader.Create(sGZip);
                    if (reader.Read())
                        return true;
                }
            }
            catch
            { }

            return false;
        }        

        // TODO: min & max should be taken for the entire available area from the provider.
        public override IGrid2dDataSource CreateDataSource(EsseParameterDescription parameter, DateTime when)
        {
            int lonPoints = (int)((this.metadataProvider.SpatialGrid.Domain.WidthLongitude) / GridStep.Longitude) + 1;
            int latPoints = (int)((this.metadataProvider.SpatialGrid.Domain.HeightLatitude) / GridStep.Latitude) + 1;
            ScalarArray2d values = new ScalarArray2d(lonPoints, latPoints);
            XmlReader valuesReader = rsp.GetXmlReaderForData(parameter, when);
            string valueString = valuesReader.ReadString();

            string[] svalues = valueString.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
            if (svalues.Length != latPoints * lonPoints)
                throw new Exception("Wrong response. Incorrent number of points " + svalues.Length + " != " + (latPoints) + " * " + (lonPoints));

            int idx = 0;

            switch (rsp.Source)
            {
                case EsseResponse.DataSource.ERA:
                case EsseResponse.DataSource.NCEP:
                    for (int i = latPoints; --i >= 0; )
                        for (int j = 0; j < lonPoints; j++)
                            values[j, i] = Double.Parse(svalues[idx++], System.Globalization.CultureInfo.InvariantCulture);
                    break;
                case EsseResponse.DataSource.SST:
                case EsseResponse.DataSource.Default:
                    for (int i = 0; i < latPoints; i++)
                        for (int j = 0; j < lonPoints; j++)
                            values[j, i] = Double.Parse(svalues[idx++], System.Globalization.CultureInfo.InvariantCulture);
                    break;
            }
         
            GeoUniformGrid2d grid = new GeoUniformGrid2d(
                new UniformGrid1d(lonPoints, GridMin.Longitude, GridStep.Longitude),
                new UniformGrid1d(latPoints, GridMin.Latitude, GridStep.Latitude));
            valuesReader.Close();
            return new ScalarFieldDataSource(new GeoUniformScalarField2d(grid, values));
        }
    }

    public class ScalarFieldDataSource : IGrid2dDataSource
    {
        GeoUniformScalarField2d field;

        public ScalarFieldDataSource(GeoUniformScalarField2d field)
        {
            this.field = field;
        }

        #region IGrid2dDataSource Members

        public IGrid2d GetGrid()
        {
            return field.Grid;
        }

        public GeoRect DataDefinitionRectangle
        {
            get
            {
                GeoUniformGrid2d geogrid = field.Grid as GeoUniformGrid2d;
                return new GeoRect(geogrid.FirstPoint, geogrid.LastPoint);

                //return new System.Drawing.RectangleF(
                //    (float)field.Grid.X.Origin, (float)field.Grid.Y.Origin,
                //    (float)(field.Grid.X.Knots[field.Grid.X.Length - 1] - field.Grid.X.Knots[0]),
                //    (float)(field.Grid.Y.Knots[field.Grid.Y.Length - 1] - field.Grid.Y.Knots[0]));
            }
        }

        public object GetData(GeoRect region)
        {
            // 1. We have to check whether requested region has intersection with
            //    our rectangle or not.
            GeoRect rect = this.DataDefinitionRectangle;
            if (region.Contains(rect))
                return field;

            GeoRect[] rects = GeoRect.Intersect(rect, region);
            if (rects.Length == 0)
                throw new VisualEsse.DataEngine.DataIsNotAvailableException();

            // 2. Requesting the data.
            GeoUniformScalarField2d selection = field.SelectData(rects[0]);
            return selection;
        }

        //public object GetData(GeoRect region)
        //{
        //    // 1. We have to check whether requested region has intersection with
        //    //    our rectangle or not.
        //    RectangleF rect = this.DataDefinitionRectangle;
        //    if (region.Contains(rect))
        //        return field;

        //    rect.Intersect(region);
        //    if (rect.IsEmpty)
        //        throw new VisualEsse.DataEngine.DataIsNotAvailableException();

        //    // 2. Requesting the data.
        //    int startI = (int)Math.Floor((rect.Left - field.Grid.X.Origin) / field.Grid.X.Step);
        //    int startJ = (int)Math.Floor((rect.Top - field.Grid.Y.Origin) / field.Grid.Y.Step);
        //    int endI = (int)Math.Ceiling((rect.Right - field.Grid.X.Origin) / field.Grid.X.Step);
        //    int endJ = (int)Math.Ceiling((rect.Bottom - field.Grid.Y.Origin) / field.Grid.Y.Step);
        //    if (endI >= field.Grid.Width)
        //        endI = field.Grid.Width - 1;
        //    if (endJ >= field.Grid.Height)
        //        endJ = field.Grid.Height - 1;

        //    UniformGrid1d sgX = new UniformGrid1d(endI - startI + 1, field.Grid.X.Knots[startI], field.Grid.X.Step);
        //    UniformGrid1d sgY = new UniformGrid1d(endJ - startJ + 1, field.Grid.Y.Knots[startJ], field.Grid.Y.Step);
        //    UniformGrid2d selGrid = new UniformGrid2d(sgX, sgY);

        //    ScalarArray2d selData = new ScalarArray2d(sgX.Length, sgY.Length);
        //    double[,] a = selData.Data;

        //    for (int i = 0; startI <= endI; startI++, i++)
        //        for (int j = 0, J = startJ; J <= endJ; J++, j++)
        //        {
        //            a[i, j] = field.Data[startI, J];
        //        }

        //    UniformScalarField2d selection = new UniformScalarField2d(selGrid, selData);
        //    return selection;
        //}

        #endregion

        #region IDataSource Members

        public object GetData()
        {
            return field;
        }

        #endregion

    }
}